跳到主要内容

Kubernetes Pod 相关

不可变基础设施是啥?

在以往传统的开发运维体系中,软件开发完成后,需要工程师或管理员通过SSH 连接到他们的服务器上,然后进行一些脚本安装、deb/rpm 包的安装工作,并逐个机器地调整对应的配置参数及文件。后续还会根据需要对该环境进行不断更改,比如 kernel 升级、配置更新、打补丁等。

随着这种类似变更的操作越来越多,没有人能弄清楚这个环境具体经历了哪些操作,而后续的变更也经常会遇到各种意想不到的诡异事情,比如软件包的循环依赖、参数的配置不一致、版本漂移等问题。

基础设施会变得越来越脆弱、敏感,一些小的改动都有可能引发大的不可预知的结果,这令广大开发者和环境管理员异常抓狂,他们需要凭借自己丰富的技术积累,耗费大量的时间去排查解决。

通常来说,这种可变基础设施会导致以下问题:

  • 持续的变更修改给服务运行态引入过多的中间态,增加了不可预知的风险;
  • 故障发生时,难以及时快速构建出新的服务副本;
  • 不易标准化,交付运维过程异常痛苦,虽然可以通过 Ansible、Puppet 等部署工具进行交付,但是也很难保证对底层各种异构的环境支持得很好,还有随时会出现的版本漂移问题。比如你可能经常遇到的,某个软件包几个月之前安装还能够正常运行,现在到一个新环境安装后,竟然无法正常工作了。

不可变基础设施则是另一种思路,部署完成以后,便成为一种只读状态,不可对其进行任何更改。如果需要更新或修改,就使用新的环境或服务器去替代旧的。不可变基础设施带来了更一致、更可靠、更可预测的设计理念,可以缓解或完全避免可变基础设施中遇到的各种常见问题。

同时,借助容器技术我们可以自动化地构建出不可变的、可版本化管理的、可一致性交付的应用服务体系,这里包括了标准化实例、运行环境等。还可以依赖持续部署系统,进行应用服务的自动化部署更新,加快迭代和部署效率。

Kubernetes 中的不可变基础设施就是 Pod。

Pod 是个啥?

Pod 由一个或多个容器组成,Pod 中的容器不可分割,会作为一个整体运行在一个 Node 节点上,也就是说 Pod 是你在 Kubernetes 中可以创建和部署的最原子化的单位。

如下不能单独列出容器,但是可以列出 Pod

$ kubectl get pods -A
NAME READY STATUS RESTARTS AGE
kubia 1/1 Running 0 30m
nginx-7cbb8cd5d8-2jgcp 1/1 Running 1 (42m ago) 4d2h
备注

同一个 Pod 中的容器共享网络、存储资源。

  • 每个 Pod 都会拥有一个独立的网络空间,其内部的所有容器都共享网络资源,即 IP 地址、端口。内部的容器直接通过 localhost 就可以通信。
  • Pod 可以挂载多个共享的存储卷(Volume),这时内部的各个容器就可以访问共享的 Volume 进行数据的读写。

每个 pod 就像一个独立的逻辑机器,拥有自己的 IP、主机名、进程等,运行一个独立的应用程序。(注意这个 IP 是集群内部的,不能从集群外部访问)

这个运行的应用程序可以是单个进程,运行在单个容器中,也可以是一个主应用进程加其他支持进程,每个进程都在自己的容器中运行。

为什么要允许一个 Pod 内可以包含多个容器?

再回答这个问题之前,我们思考一下另外一个问题 “为什么不直接在单个容器里运行多个程序?”。

由于容器实际上是一个 “单进程” 的模型,这点非常重要。因为如果你在容器里启动多个进程,这将会带来很多麻烦。不仅它们的日志记录会混在一起,它们各自的生命周期也无法管理。毕竟只有一个进程的 PID 可以为 1,如果 PID 为 1 的进程这个时候挂了,或者说失败退出了,那么其他几个进程就会自然而然地成为 “孤儿”,无法管理,也无法回收资源。

很多公司在刚开始容器化改造的时候,都会这么去使用容器,把容器当作 VM 来使用,有时候也叫作富容器模式。这其实是一种非常不好的尝试,也不符合不可变基础设施的理念。我们可以接受将富容器当作容器化改造的一个短暂的过渡形态,但不能将其作为改造的终态。后续,还需要进一步对这些富容器进行拆分、解耦。

看到这里,第二个问题的答案已经呼之欲出了。用一个 Pod 管理多个容器,既能够保持容器之间的隔离性,还能保证相关容器的环境一致性。使用粒度更小的容器,不仅可以使应用间的依赖解耦,还便于使用不同技术栈进行开发,同时还可以方便各个开发团队复用,减少重复造轮子。

Pod 里面的容器组合原则

既然一个 Pod 内支持定义多个容器,是不是意味着我可以任意组合,甚至将无关紧要的容器放进来都无所谓?不!这不是我们推荐的方式,也不是使用 Pod 的正确打开方式。

通常来说,如果在一个 Pod 内有多个容器,那么这几个容器最好是密切相关的,且可以共享一些资源的,比如网络、存储等。

来看看 官方文档中给的一个例子。这个 Pod 里面运行了两个容器 File Puller 和 Web Server。其中 File Puller 负责定期地从外部 Content Manager 同步内容,更新到挂载的共享存储卷(Volume)中,而 Web Server 只负责对外提供访问服务。两个容器之间通过共享的存储卷共享数据。

例如,你可能有一个容器,为共享卷中的文件提供 Web 服务器支持,以及一个单独的 "边车 (sidercar)" 容器负责从远端更新这些文件,如下图所示:

有些 Pod 具有 Init 容器和 应用容器。 Init 容器会在启动应用容器之前运行并完成。

Pod 天生地为其成员容器提供了两种共享资源:网络和存储。

提示

值得注意的是,当一个 pod 包含多个容器时,这些容器总是运行于同一个工作节点上,一个 Pod 绝不会跨越多个工作节点

类似这样紧密耦合的业务容器,就比较适合放置在同一个 Pod 中,可以保证很高的通信效率。

一般来说,在一个 Pod 内运行多个容器,比较适应于以下这些场景。

1、容器之间会发生文件交换等,上面提到的例子就是这样。一个写文件,一个读文件。

2、容器之间需要本地通信,比如通过 localhost 或者本地的 Socket。这种方式有时候可以简化业务的逻辑,因为此时业务就不用关心另外一个服务的地址,直接本地访问就可以了。

3、容器之间需要发生频繁的 RPC 调用,出于性能的考量,将它们放在一个 Pod 内。

4、希望为应用添加其他功能,比如日志收集、监控数据采集、配置中心、路由及熔断等功能。这时候可以考虑利用边车模式(Sidecar Pattern),既不需要改动原始服务本身的逻辑,还能增加一系列的功能。比如 Fluentd 就是利用边车模式注入一个对应 log agent 到 Pod 内,用于日志的收集和转发。 Istio 也是通过在 Pod 内放置一个 Sidecar 容器,来进行无侵入的服务治理。

如何声明一个 Pod

在 Kubernetes 中,所有对象都可以通过一个相似的 API 模板来描述,它们主要包含5个部分:

  • apiVersion:版本,有 kubernetes 内部定义,版本号必须用 kubectl api-versions 查询。
  • kind:类型,有 kubernetes 内部定义,类型必须用 kubectl api-resources 查询。
  • metadata:元数据,主要是资源标识和说明,常用的有 name、namespace、labels 等。
  • spec:描述,这是配置中最重要的一部分,里面是对各种资源配置的详细描述。
  • status:状态信息,里面的内容不需要定义,由 kubernetes 自动生成。

Kubernetes 有了这种统一风格的 API 定义,方便了通过 REST 接口进行开发和管理。

可以通过 explain 查看具体的配置项

# 查看某种资源可以配置的一级配置
kubectl explain 资源类型
# 查看属性的子属性
kubectl explain 资源类型.属性

例如查看资源类型为 pod 的可配置项

Pod 的类型 Kind

在 Kubernetes 中,Pod 是最小的部署单元,用于承载容器。以下是一些常见的 Pod 的 kind:

  • Pod:最基本的 Pod,它是由一个或多个紧密关联的容器组成的。
  • ReplicaSet:用于管理多个副本的 Pod,并确保指定数量的 Pod 始终运行。
  • Deployment:建立在 ReplicaSet 之上的控制器,用于管理应用程序的部署和更新。
  • StatefulSet:用于管理有状态应用程序的 Pod,并确保这些 Pod 按照一定的顺序启动和关闭。
  • DaemonSet:用于在集群的每个节点上运行一个副本的 Pod,通常用于运行系统级别的服务或网络代理。
  • Job:用于一次性任务的 Pod,当任务完成后自动终止。
  • CronJob:用于按照计划执行任务的 Pod。

元数据(metadata)

metadata 中一般要包含如下 3 个对该对象至关重要的元信息:namespace(命名空间)、name(对象名)和 uid(对象 ID)。

1、namespace 是 Kubernetes 中比较重要的一个概念,是对一组资源和对象的抽象集合,namespace 主要用于逻辑上的隔离。

$ kubectl get ns

Kubernetes 中有几个内置的 namespace:

  • default,这是默认的缺省命名空间;
  • kube-system,主要是部署集群最关键的核心组件,比如一般会将 CoreDNS 声明在这个 namespace 中;
  • kube-public,是由 kubeadm 创建出来的,主要是保存一些集群 bootstrap 的信息,比如 token 等;
  • kube-node-lease,它要用于 node 汇报心跳,每一个节点都会有一个对应的 Lease 对象。

2、uid 是由系统自动生成的,主要用于 Kubernetes 内部标识使用,比如某个对象经历了删除重建,单纯通过名字是无法判断该对象的新旧,这个时候就可以通过 uid 来进行唯一确定。

当然, Kubernetes 中并不是所有对象都是 namespace 级别的,还有一些对象是集群级别的,并不需要 namespace 进行隔离,比如 Node 资源等。

除此以外,还可以在 metadata 里面用各种标签 (labels)和注释(annotations)来标识和匹配不同的对象,比如用户可以用标签env=dev来标识开发环境,用env=testing来标识测试环境。

规范 (Spec)

在 Spec 中描述了该对象的详细配置信息,即用户希望的状态(Desired State)。Kubernetes 中的各大组件会根据这个配置进行一系列的操作,将这种定义从“抽象”变为“现实”,我们称之为调和(Reconcile)。用户不需要过度关心怎么达到终态,也不用参与。

继续看下它的常见子属性:

containers <[]Object>:容器列表,用于定义容器的详细信息。
nodeName <String>:根据nodeName的值将Pod调度到指定的Node节点上。
nodeSelector <map[]> :根据NodeSelector中定义的信息选择该Pod调度到包含这些Label的Node上。
hostNetwork <boolean>:是否使用主机网络模式,默认为false,如果设置为true,表示使用宿主机网络。
volumes <[]Object> :存储卷,用于定义Pod上面挂载的存储信息。
restartPolicy <string>:重启策略,表示Pod在遇到故障的时候的处理策略。

整个结构如下图所示

状态(Status)

在这个字段里面,包含了该对象的一些状态信息,会由各个控制器定期进行更新。也是不同控制器之间进行相互通信的一个渠道。

在 Kubernetes 中,各个组件都是分布式部署的,围绕着 kube-apiserver 进行通信,那么不同组件之间进行信息同步,就可以通过 status 进行。像 Node 的 status 就记录了该节点的一些状态信息,其他的控制器,就可以通过 status 知道该 Node 的情况,做一些操作,比如节点宕机修复、可分配资源等。

通过 kubectl 创建 Pod 成功后(下面那节),可以通过如下命令看到 Pod 的状态:

# 这里使用了 kubectl 命令行 JSONPATH 模板能力
$ kubectl get pod twocontainers -o=jsonpath='{.status.phase}'
Running

在整个生命周期中,Pod 会出现 5 种状态(相位),分别如下:

  • 挂起(Pending):apiserver 已经创建了 Pod 资源对象,但它尚未被调度完成或者仍处于下载镜像的过程中
  • 运行中(Running):Pod 已经被调度至某节点,并且所有容器都已经被 kubelet 创建完成
  • 成功(Succeeded):Pod 中的所有容器都已经成功终止并且不会被重启
  • 失败(Failed):所有容器都已经终止,但至少有一个容器终止失败,即容器返回了非0值的退出状态
  • 未知(Unknown):apiserver 无法正常获取到 Pod 对象的状态信息,通常由网络通信失败所导致

查看 Pod 信息

查询所有 Pod 的基本信息

kubectl get pods [-n 命名空间的名称]

查询名称为 dev 的 namespace 下的所有 Pod 的基本信息

kubectl get pods -n dev

查看 Pod 的详细信息

# kubectl describe pod nginx -n dev
kubectl describe pod <pod的名称> [-n 命名空间名称]

Pod 的访问

# 获取 Pod 的IP
kubectl get pods [-n dev] -o wide


# 通过curl访问
curl ip:端口

例如访问 Nginx 的 Pod

简单的 Pod 例子

创建一个 twocontainers.yaml 文件

apiVersion: v1 # 指定当前描述文件遵循v1版本的Kubernetes API
kind: Pod # 我们在描述一个 pod
metadata:
name: twocontainers # 指定 pod 的名称
namespace: default # 指定当前描述的 pod 所在的命名空间
labels: # 指定 pod 标签
app: twocontainers
annotations: # 指定 pod 注释
version: v0.5.0
releasedBy: david
purpose: demo
spec:
containers:
- name: simplesrv # 容器的名称
image: quay.io/openshiftlabs/simpleservice:0.5.0 # 创建容器所使用的镜像
ports:
- containerPort: 9876 # 应用监听的端口
- name: simplesrv-shell # 容器的名称
image: ubuntu:latest # 创建容器所使用的镜像
command: # 容器启动命令
- 'bin/bash'
- '-c'
- 'sleep 10000'

创建 Pod

$ kubectl create -f ./twocontainers.yaml
$ kubectl get pods

创建出来后,稍微等待一下,我们就可以看到,该 Pod 已经运行成功了。现在我们可以通过 exec 进入 simplesrv-shell 这个容器,来访问 simplesrv 服务:

# kubectl exec pod名称 -n 命名空间 -it -c 容器名称 /bin/sh
$ kubectl exec twocontainers -c simplesrv-shell -it /bin/sh

可以发现已经能访问到 simplesrv 服务了,这就是一个简单的 Pod 的例子。

$ curl -s localhost:9876/info

检查已有的 Pod 描述文件

可以通过 get 命令来检查已有的 Pod 描述文件

# -w 参数可以实时查看 Pod 的状态
$ kubectl get po nginx -o yaml -w

相对完整的描述文件介绍

下面是 Pod 的资源清单:

apiVersion: v1 # 必选,版本号,例如v1
kind: Pod # 必选,资源类型,例如 Pod
metadata: # 必选,元数据
name: string # 必选,Pod名称
namespace: string # Pod所属的命名空间,默认为 "default"
labels: # 自定义标签列表
- name: string
spec: # 必选,Pod中容器的详细定义
containers: # 必选,Pod中容器列表
- name: string # 必选,容器名称
image: string # 必选,容器的镜像名称
imagePullPolicy: [Always|Never|IfNotPresent] # 获取镜像的策略
command: # 容器的启动命令列表,如不指定,使用打包时使用的启动命令
- string
args: # 容器的启动命令参数列表
- string
workingDir: string # 容器的工作目录
volumeMounts: # 挂载到容器内部的存储卷配置
- name: string # 引用pod定义的共享存储卷的名称,需用volumes[]部分定义的的卷名
mountPath: string # 存储卷在容器内 mount 的绝对路径,应少于512字符
readOnly: boolean # 是否为只读模式
ports: # 需要暴露的端口库号列表
- name: string # 端口的名称
containerPort: int # 容器需要监听的端口号
hostPort: int # 容器所在主机需要监听的端口号,默认与Container相同
protocol: string # 端口协议,支持TCP和UDP,默认TCP
env: # 容器运行前需设置的环境变量列表
- name: string # 环境变量名称
value: string # 环境变量的值
resources: # 资源限制和请求的设置
limits: # 资源限制的设置
cpu: string # Cpu的限制,单位为 core 数,将用于docker run --cpu-shares参数
memory: string # 内存限制,单位可以为 Mib/Gib,将用于docker run --memory参数
requests: # 资源请求的设置
cpu: string # Cpu请求,容器启动的初始可用数量
memory: string # 内存请求,容器启动的初始可用数量
lifecycle: # 生命周期钩子
postStart: null # 容器启动后立即执行此钩子,如果执行失败,会根据重启策略进行重启
preStop: null # 容器终止前执行此钩子,无论结果如何,容器都会终止
livenessProbe: # 对Pod内各容器健康检查的设置,当探测无响应几次后将自动重启该容器
exec: # 对Pod容器内检查方式设置为exec方式
command: # exec 方式需要制定的命令或脚本
- string
httpGet: # 对Pod内个容器健康检查方法设置为 HttpGet,需要制定 Path、port
path: string
port: number
host: string
scheme: string
HttpHeaders:
- name: string
value: string
tcpSocket: # 对Pod内个容器健康检查方式设置为tcpSocket方式
port: number
initialDelaySeconds: 0 # 容器启动完成后首次探测的时间,单位为秒
timeoutSeconds: 0 # 对容器健康检查探测等待响应的超时时间,单位秒,默认1秒
periodSeconds: 0 # 对容器监控检查的定期探测时间设置,单位秒,默认10秒一次
successThreshold: 0
failureThreshold: 0
securityContext:
privileged: false
restartPolicy: [Always | Never | OnFailure] # Pod的重启策略
nodeName: <string> # 设置 NodeName 表示将该Pod调度到指定到名称的node节点上
nodeSelector: object # 设置NodeSelector表示将该Pod调度到包含这个label的node上
imagePullSecrets: # Pull 镜像时使用的 secret 名称,以 key:secretkey 格式指定
- name: string
# 是否使用主机网络模式,默认为 false,如果设置为 true,表示使用宿主机网络
hostNetwork: false
# 在该pod上定义共享存储卷列表
volumes: # 共享存储卷名称 (volumes 类型有很多种)
- name: string
emptyDir: {} # 类型为 emptyDir 的存储卷,与Pod同生命周期的一个临时目录。为空值
hostPath: string # 类型为 hostPath 的存储卷,表示挂载 Pod 所在宿主机的目录
path: string # Pod 所在宿主机的目录,将被用于同期中 mount 的目录
secret: # 类型为secret的存储卷,挂载集群与定义的secret对象到容器内部
scretname: string
items:
- key: string
path: string
configMap: # 类型为configMap的存储卷,挂载预定义的configMap对象到容器内部
name: string
items:
- key: string
path: string

Pod 的补充知识

共享 IPC 命名空间

容器中进程交互还是采用了 Linux 常见的进程间交互方法 IPC(Inter-Process Communication,进程间通信),包括信号量、消息队列和共享内存等。

然而同 VM 不同的是,容器的进程间交互实际上还是 host 上具有相同 pid 命名空间中的进程间交互,因此需要在 IPC 资源申请时加入命名空间信息,每个 IPC 资源有一个唯一的 32 位 id。

提示

不同用户的进程就是通过 pid 命名空间隔离开的,且不同命名空间中可以有相同 pid。所有的 LXC 进程在 Docker 中的父进程为 Docker 进程,每个 LXC 进程具有不同的命名空间。同时由于允许嵌套,因此可以很方便的实现嵌套的 Docker 容器。

共享 Network 命名空间

由于一个 pod 中的容器运行于相同的 Network 命名空间中,因此它们共享相同的 IP 地址和端口空间。 这意味着在同一 pod 中的容器运行的多个进程需要注意不能绑定到相同的端口号,否则会导致端口冲突,但这只涉及同一 pod 中的容器。

由于每个 pod 都有独立的端口空间,对于不同 pod 中的容器来说则永远不会遇到端口冲突。此外, 一个 pod 中的所有容器也都具有相同的 loopback 网络接口,因此容器可以通过 localhost 与同一 pod 中的其他容器进行通信。

Pod 的具体使用

查看 pod.spec.containers 的可选配置项

kubectl explain pod.spec.containers
# 返回的重要属性
KIND:     Pod
VERSION:  v1
RESOURCE: containers <[]Object>   # 数组,代表可以有多个容器FIELDS:
name  <string>     # 容器名称
image <string>     # 容器需要的镜像地址
imagePullPolicy  <string> # 镜像拉取策略
command  <[]string> # 容器的启动命令列表,如不指定,使用打包时使用的启动命令
args   <[]string> # 容器的启动命令需要的参数列表
env   <[]Object> # 容器环境变量的配置
ports <[]Object> # 容器需要暴露的端口号列表
resources <Object> # 资源限制和资源请求的设置

创建一个基本的 Pod

创建 pod-base.yaml 文件,内容如下:

apiVersion: v1
kind: Pod
metadata:
name: pod-base
namespace: dev
labels:
user: alsritter
spec:
containers:
- name: nginx # 容器名称
image: nginx:1.17.1 # 容器需要的镜像地址
- name: busybox # 容器名称
image: busybox:1.30 # 容器需要的镜像地址

上面定义了一个比较简单的 Pod 的配置,里面有两个容器:

  • nginx:用的是1.17.1版本的nginx镜像创建
  • busybox:用的是1.30版本的busybox镜像创建

创建 Pod:

kubectl apply -f pod-base.yaml

查看 Pod 状况:

kubectl get pod -n dev

通过 describe 查看内部的详情:

# 此时已经运行起来了一个基本的Pod,虽然它暂时有问题
kubectl describe pod pod-base -n dev

或者使用 create 与 delete 执行创建和删除命令:

kubectl create -f pod-nginx.yaml

kubectl delete -f pod-nginx.yaml

检查 Pod 的状态

查看 pod 状态:可以使用 kubectl get pods 命令查看当前集群中所有的 pod 以及它们的状态,例如:

kubectl get pods

# 输出:
NAME READY STATUS RESTARTS AGE
nginx-6f76d4d9c5-2sb8j 1/1 Running 0 30s
nginx-6f76d4d9c5-7krqn 1/1 Running 0 30s

1、查看 pod 日志:可以使用 kubectl logs 命令查看 pod 日志,例如:

kubectl logs nginx-6f76d4d9c5-2sb8j

# 输出:
nginx version: nginx/1.19.9
...

如果你想实时监视 pod 日志的输出,可以使用 kubectl logs -f 命令,例如:kubectl logs -f nginx-6f76d4d9c5-2sb8j。

2、进入 pod 内部:可以使用 kubectl exec 命令进入 pod 内部进行操作,例如:

kubectl exec -it nginx-6f76d4d9c5-2sb8j /bin/bash

# 输出:
root@nginx-6f76d4d9c5-2sb8j:/#

上面的命令会打开一个 shell 终端,并进入到 nginx-6f76d4d9c5-2sb8j 这个 pod 的容器内部。你可以在这个终端里面执行命令来进行调试和操作。

3、查看 pod 的详细信息:可以使用 kubectl describe 命令查看 pod 的详细信息,例如:

kubectl describe pod <Pod名称> -n 命名空间

查看具体的容器日志

# 想查看 Pods 下面的容器
kubectl get pods -n dev
kubectl describe pod <Pod名称> -n 命名空间
kubectl logs <pod_name> -c <container_name>

# 假如当前pod只有一个容器,运行以下命令即可
kubectl exec -it nginx-56b8c64cb4-t97vb -- /bin/bash

通过命令来创建删除 Pod

kubernetes 没有提供单独运行 Pod 的命令,都是通过 Pod 控制器来实现的

# --image      指定 Pod 的镜像
# --port 指定端口
# --namespace 指定 namespace
kubectl run nginx --image=nginx:latest --port=80 --namespace dev

删除指定的 Pod(花费的时间有点久)

# kubectl delete pod nginx -n dev
kubectl delete pod <pod的名称> [-n 命名空间]

# 检查是否被删除了
kubectl get pods --all-namespaces --no-headers --watch-only

镜像的拉取策略

创建 pod-imagepullpolicy.yaml 文件,内容如下:

apiVersion: v1
kind: Pod
metadata:
name: pod-imagepullpolicy
namespace: dev
labels:
user: alsritter
spec:
containers:
- name: nginx # 容器名称
image: nginx:1.17.1 # 容器需要的镜像地址
imagePullPolicy: Always # 用于设置镜像的拉取策略
- name: busybox # 容器名称
image: busybox:1.30 # 容器需要的镜像地址

这个 imagePullPolicy 用于设置镜像拉取的策略,kubernetes 支持配置三种拉取策略:

  • Always:总是从远程仓库拉取镜像(一直远程下载)。
  • IfNotPresent:本地有则使用本地镜像,本地没有则从远程仓库拉取镜像(本地有就用本地,本地没有就使用远程下载)。
  • Never:只使用本地镜像,从不去远程仓库拉取,本地没有就报错(一直使用本地,没有就报错)。

默认值说明:

  • 如果镜像 tag 为具体的版本号,默认策略是 IfNotPresent。
  • 如果镜像 tag 为latest(最终版本),默认策略是 Always。

创建 Pod:

kubectl apply -f pod-imagepullpolicy.yaml

通过 describe 查看内部的详情:

kubectl describe pod pod-imagepullpolicy -n dev

output:

Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled <unknown> default-scheduler Successfully assigned dev/pod-imagePullPolicy to node1
Normal Pulling 32s kubelet, node1 Pulling image "nginx:1.17.1"
Normal Pulled 26s kubelet, node1 Successfully pulled image "nginx:1.17.1"
Normal Created 26s kubelet, node1 Created container nginx
Normal Started 25s kubelet, node1 Started container nginx
Normal Pulled 7s (x3 over 25s) kubelet, node1 Container image "busybox:1.30" already present on machine
Normal Created 7s (x3 over 25s) kubelet, node1 Created container busybox
Normal Started 7s (x3 over 25s) kubelet, node1 Started container busybox

通过执行定时任务来保活

在前面的案例中,一直有一个问题没有解决,就是 busybox 容器一直没有成功运行,那么到底是什么原因导致这个容器的故障呢?

原来 busybox 并不是一个程序,而是类似于一个工具类的集合,kubernetes 集群启动管理后,它会自动关闭。

所以解决方法就是让其一直在运行,这就用到了 command 配置。

创建 pod-command.yaml 文件,内容如下:

apiVersion: v1
kind: Pod
metadata:
name: pod-command
namespace: dev
labels:
user: alsritter
spec:
containers:
- name: nginx # 容器名称
image: nginx:1.17.1 # 容器需要的镜像地址
imagePullPolicy: IfNotPresent # 设置镜像拉取策略
- name: busybox # 容器名称
image: busybox:1.30 # 容器需要的镜像地址
command: ["/bin/sh","-c","touch /tmp/hello.txt;while true;do /bin/echo $(date +%T) >> /tmp/hello.txt;sleep 3;done;"]

command:用于在 Pod 中的容器初始化完毕之后执行一个命令

# 上面的这句命令表示每隔3秒,向文件写入当前时间
while true; do /bin/echo $(date +%T) >> /tmp/hello.txt; sleep 3; done
# 创建Pod
$ kubectl create -f pod-command.yaml

# 查看Pod状态
# 此时发现两个pod都正常运行了
$ kubectl get pods pod-command -n dev
NAME READY STATUS RESTARTS AGE
pod-command 2/2 Runing 0 2s

进入 pod 中的 busybox 容器,查看文件内容,这里补充一个命令:

# 进入容器
kubectl exec pod名称 -n 命名空间 -it -c 容器名称 /bin/sh

使用这个命令就可以进入某个容器的内部,然后进行相关操作了,比如,可以查看 txt 文件的内容

$ kubectl exec pod-command -n dev -it -c busybox /bin/sh 
>
> tail -f /tmp/hello.txt
14:44:19
14:44:22
14:44:25

特别说明:通过上面发现 command 已经可以完成启动命令和传递参数的功能,为什么这里还要提供一个 args 选项,用于传递参数呢?

这其实跟 docker 有点关系,kubernetes 中的 command、args 两项其实是实现覆盖 Dockerfile 中 ENTRYPOINT 的功能。

  1. 如果 command 和 args 均没有写,那么用 Dockerfile 的配置。
  2. 如果 command 写了,但 args 没有写,那么 Dockerfile 默认的配置会被忽略,执行输入的 command
  3. 如果 command 没写,但 args 写了,那么 Dockerfile 中配置的 ENTRYPOINT 的命令会被执行,使用当前 args 的参数
  4. 如果 command 和 args 都写了,那么 Dockerfile 的配置被忽略,执行 command 并追加上 args 参数

容器的端口设置

本小节来介绍容器的端口设置,也就是 containers 的 ports 选项。

$ kubectl explain pod.spec.containers.ports
KIND: Pod
VERSION: v1
RESOURCE: ports <[]Object>
FIELDS:
name <string> # 端口名称,如果指定,必须保证name在pod中是唯一的
containerPort<integer> # 容器要监听的端口(0<x<65536)
hostPort <integer> # 容器要在主机上公开的端口,如果设置,主机上只能运行容器的一个副本(一般省略)
hostIP <string> # 要将外部端口绑定到的主机IP(一般省略)
protocol <string> # 端口协议。必须是UDP、TCP或SCTP。默认为“TCP”。

接下来,编写一个测试案例,创建 pod-ports.yaml

apiVersion: v1
kind: Pod
metadata:
name: pod-ports
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports: # 设置容器暴露的端口列表
- name: nginx-port
containerPort: 80
protocol: TCP

此时访问容器中的程序使用的是

Podip:containerPort

资源配额

容器中的程序要运行,肯定是要占用一定资源的,比如 CPU 和内存等,如果不对某个容器的资源做限制,那么它就可能吃掉大量资源,导致其它容器无法运行。针对这种情况,kubernetes 提供了对内存和 CPU 的资源进行配额的机制,这种机制主要通过 resources 选项实现,他有两个子选项:

  • limits:用于限制运行时容器的最大占用资源,当容器占用资源超过 limits 时会被终止,并进行重启
  • requests :用于设置容器需要的最小资源,如果环境资源不够,容器将无法启动

可以通过上面两个选项设置资源的上下限。

接下来,编写一个测试案例,创建 pod-resources.yaml

apiVersion: v1
kind: Pod
metadata:
name: pod-resources
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
resources: # 资源配额
limits: # 限制资源(上限)
cpu: "2" # CPU限制,单位是core数
memory: "10Gi" # 内存限制
requests: # 请求资源(下限)
cpu: "1" # CPU限制,单位是core数
memory: "10Mi" # 内存限制

在这对 cpu 和 memory 的单位做一个说明:

  • cpu:core 数,可以为整数或小数
  • memory:内存大小,可以使用Gi、Mi、G、M等形式

在外部让容器执行命令

kubectl exec kubia-7nog1 -- curl -s http://baidu.com

这个 kubia-7nog1 表示具体的 Pod 容器,后面跟随的这个 -- 后面的内容表示 Pod 内部需要执行的命令,如果不加 --,那么 kubectl 可能会把 -s 参数解析成 kubectl 自己的 -s 选项

钩子函数

钩子函数能够感知自身生命周期中的事件,并在相应的时刻到来时运行用户指定的程序代码。

重新拉取 kubernetes Pods 镜像

kubectl get pod PODNAME -n NAMESPACE -o yaml | kubectl replace --force -f -

References